﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using TanmiaGrp.Basis;
using TanmiaGrp.Basis.Core;
using TanmiaGrp.Basis.Specification;
using TanmiaGrp.GeneralLedger.IRepository.Basis;
using RefactorThis.GraphDiff;

namespace TanmiaGrp.GeneralLedger.SqlServerRepository
{
    public class GenericQueryRepository : IGenericQueryRepository
    {
        private GLContext glContext;

        public GenericQueryRepository() { }

        public GenericQueryRepository(GLContext context)
        {
            this.glContext = context;
        }
        #region IGenericQueryRepository Members

        /// <summary>
        /// Returns single BusinessObject of table that satisfies a specified condition or a null if no such element is found.
        /// </summary>
        /// <typeparam name="T">The type of BusinessObject of source table.</typeparam>
        /// <param name="predicate">A function to test each element for a condition.</param>
        /// <returns>The BusinessObject the satisfy the predicate.</returns>
        public T Single<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            try
            {
                GLContext context = glContext ?? new GLContext();

                Func<T, bool> predicateFunc = predicate.Compile();
                T result = context.LoadAggregate(predicateFunc);

                if (glContext == null)
                    context.Dispose();
                // else the management of the context is being done by the UnitOfWork

                return result;
            }
            catch (Exception ex)
            {
                throw ThrowHelper.ReThrow(ex);
            }
        }

        public T Load<T>(ISpecification<T> spec)
            where T : class
        {
            GLContext context = glContext ?? new GLContext();
            BaseSpecification<T> baseSpec = spec as BaseSpecification<T>;

            IQueryable<T> query = GetQuery<T>(context, baseSpec.FetchStartegy);

            T result = query.FirstOrDefault(baseSpec.Predicate);

            if (glContext == null)
                context.Dispose();
            // else the management of the context is being done by the UnitOfWork

            return result;
        }

        public List<T> LoadAll<T>(ISpecification<T> spec)
            where T : class
        {
            GLContext context = glContext ?? new GLContext();
            BaseSpecification<T> baseSpec = spec as BaseSpecification<T>;

            IQueryable<T> query = GetQuery<T>(context, baseSpec.FetchStartegy);

            // apply paging if parameters are specified.
            if (baseSpec.PageIndex >= 0 & baseSpec.PageSize > 0)
                query = query
                    .Skip(baseSpec.PageIndex * baseSpec.PageSize)
                    .Take(baseSpec.PageSize);

            // apply filter
            if (baseSpec.Predicate != null)
                query = query.Where(baseSpec.Predicate);

            List<T> result = query.ToList();

            if (glContext == null)
                context.Dispose();
            // else the management of the context is being done by the UnitOfWork

            return result;
        }

        public bool Matches<T>(ISpecification<T> spec)
            where T : class
        {
            GLContext context = glContext ?? new GLContext();

            BaseSpecification<T> baseSpec = spec as BaseSpecification<T>;

            IQueryable<T> query = GetQuery<T>(context, baseSpec.FetchStartegy);

            bool result = query.Any(baseSpec.Predicate);

            if (glContext == null)
                context.Dispose();
            // else the management of the context is being done by the UnitOfWork

            return result;
        }

        /// <summary>
        /// Returns the number of elements in a BusinessObject's table.
        /// </summary>
        /// <typeparam name="T">The type of BusinessObject of source table.</typeparam>
        /// <returns>The number of elements in the input sequence.</returns>
        public int GetCount<T>() where T : class
        {
            try
            {
                GLContext context = glContext ?? new GLContext();

                int result = context.Set<T>().Count();

                if (glContext == null)
                    context.Dispose();
                // else the management of the context is being done by the UnitOfWork

                return result;
            }
            catch (Exception ex)
            {
                throw ThrowHelper.ReThrow(ex);
            }
        }

        /// <summary>
        /// Returns the number of elements of the specified T BusinessEntity that satisfies a condition.
        /// </summary>
        /// <typeparam name="T">The type of BusinessObject of source table.</typeparam>
        /// <param name="predicate">A function to test each element for a condition.</param>
        /// <returns>The number of elements in the sequence that satisfies the condition in the predicate function.</returns>
        public int GetCount<T>(Expression<Func<T, bool>> predicate)
            where T : class
        {
            try
            {
                GLContext context = glContext ?? new GLContext();

                int result = context.Set<T>().Count(predicate);

                if (glContext == null)
                    context.Dispose();
                // else the management of the context is being done by the UnitOfWork

                return result;
            }
            catch (Exception ex)
            {
                throw ThrowHelper.ReThrow(ex);
            }
        }

        public List<T> LoadAll<T, TKey>(ISpecification<T> spec, Expression<Func<T, TKey>> orderBy) where T : class
        {
            GLContext context = glContext ?? new GLContext();

            BaseSpecification<T> baseSpec = spec as BaseSpecification<T>;

            IQueryable<T> query = GetDbQuery<T>(context, baseSpec.FetchStartegy);

            query = query.OrderBy(orderBy);

            // apply paging if parameters are specified.
            if (baseSpec.PageIndex >= 0 & baseSpec.PageSize > 0)
                query = query
                    .Skip(baseSpec.PageIndex * baseSpec.PageSize)
                    .Take(baseSpec.PageSize);

            // apply filter
            if (baseSpec.Predicate != null)
                query = query.Where(baseSpec.Predicate);

            List<T> result = query.AsNoTracking().ToList();
            result.Cast<BusinessObject>().ToList().ForEach(x => x.ClearTracking());

            if (glContext == null)
                context.Dispose();
            // else the management of the context is being done by the UnitOfWork

            return result;
        }

        #endregion

        private IQueryable<T> GetQuery<T>(GLContext context, IFetchStrategy<T> fetchStrategy)
            where T : class
        {
            DbSet<T> dbset = context.Set<T>();

            if (fetchStrategy == null)
                return dbset.AsNoTracking();

            foreach (string path in fetchStrategy.IncludePaths)
                dbset.Include(path);

            return dbset;
        }

        private DbQuery<T> GetDbQuery<T>(GLContext context, IFetchStrategy<T> fetch) where T : class
        {
            DbQuery<T> dbquery = context.Set<T>();

            if (fetch == null)
                return context.Set<T>();

            foreach (string item in fetch.IncludePaths)
                dbquery = dbquery.Include(item);

            return dbquery;
        }
    }
}